package com.alogic.commonmark;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.anysoft.util.Properties;
import org.commonmark.Extension;
import org.commonmark.internal.renderer.NodeRendererMap;
import org.commonmark.internal.util.Escaping;
import org.commonmark.node.Node;
import org.commonmark.renderer.NodeRenderer;
import org.commonmark.renderer.Renderer;
import org.commonmark.renderer.html.*;

/**
 * 定制的html renderer
 *
 * @since 1.6.13.11 [20200901 duanyy]
 */
public class HtmlRenderer implements Renderer {
    private final String softbreak;
    private final boolean escapeHtml;
    private final boolean sanitizeUrls;
    private final UrlSanitizer urlSanitizer;
    private final boolean percentEncodeUrls;
    private final List<AttributeProviderFactory> attributeProviderFactories;
    private final List<HtmlNodeRendererFactory> nodeRendererFactories;

    private HtmlRenderer(Builder builder) {
        this.softbreak = builder.softbreak;
        this.escapeHtml = builder.escapeHtml;
        this.sanitizeUrls = builder.sanitizeUrls;
        this.percentEncodeUrls = builder.percentEncodeUrls;
        this.urlSanitizer = builder.urlSanitizer;
        this.attributeProviderFactories = new ArrayList(builder.attributeProviderFactories);
        this.nodeRendererFactories = new ArrayList(builder.nodeRendererFactories.size() + 1);
        this.nodeRendererFactories.addAll(builder.nodeRendererFactories);
        this.nodeRendererFactories.add(new HtmlNodeRendererFactory() {
            public NodeRenderer create(HtmlNodeRendererContext context) {
                return new CoreHtmlNodeRenderer(context);
            }
        });
    }

    public static Builder builder() {
        return new Builder();
    }

    public void render(Node node, Appendable output) {
        if (node == null) {
            throw new NullPointerException("node must not be null");
        } else {
            RendererContext context = new RendererContext(new HtmlWriter(output),null);
            context.render(node);
        }
    }

    public String render(Node node) {
        if (node == null) {
            throw new NullPointerException("node must not be null");
        } else {
            StringBuilder sb = new StringBuilder();
            this.render(node, sb);
            return sb.toString();
        }
    }

    public void render(Node node, Appendable output,Properties ctx) {
        if (node == null) {
            throw new NullPointerException("node must not be null");
        } else {
            RendererContext context = new RendererContext(new HtmlWriter(output),ctx);
            context.render(node);
        }
    }

    public String render(Node node,Properties ctx) {
        if (node == null) {
            throw new NullPointerException("node must not be null");
        } else {
            StringBuilder sb = new StringBuilder();
            this.render(node, sb, ctx);
            return sb.toString();
        }
    }

    private class RendererContext implements HtmlNodeRendererContext, AttributeProviderContext {
        private final HtmlWriter htmlWriter;
        private final List<AttributeProvider> attributeProviders;
        private final NodeRendererMap nodeRendererMap;
        private final Properties ctx;

        private RendererContext(HtmlWriter htmlWriter, Properties ctx) {
            this.ctx = ctx;
            this.nodeRendererMap = new NodeRendererMap();
            this.htmlWriter = htmlWriter;
            this.attributeProviders = new ArrayList(HtmlRenderer.this.attributeProviderFactories.size());
            Iterator iterator = HtmlRenderer.this.attributeProviderFactories.iterator();
            while(iterator.hasNext()) {
                AttributeProviderFactory attributeProviderFactory = (AttributeProviderFactory)iterator.next();
                this.attributeProviders.add(attributeProviderFactory.create(this));
            }
            for(int i = HtmlRenderer.this.nodeRendererFactories.size() - 1; i >= 0; --i) {
                HtmlNodeRendererFactory nodeRendererFactory = (HtmlNodeRendererFactory) HtmlRenderer.this.nodeRendererFactories.get(i);
                NodeRenderer nodeRenderer = nodeRendererFactory.create(this);
                this.nodeRendererMap.add(nodeRenderer);
            }
        }

        public boolean shouldEscapeHtml() {
            return HtmlRenderer.this.escapeHtml;
        }

        public boolean shouldSanitizeUrls() {
            return HtmlRenderer.this.sanitizeUrls;
        }

        public UrlSanitizer urlSanitizer() {
            return HtmlRenderer.this.urlSanitizer;
        }

        public String encodeUrl(String url) {
            return HtmlRenderer.this.percentEncodeUrls ? Escaping.percentEncodeUrl(url) : url;
        }

        public Map<String, String> extendAttributes(Node node, String tagName, Map<String, String> attributes) {
            Map<String, String> attrs = new LinkedHashMap(attributes);
            this.setCustomAttributes(node, tagName, attrs);
            return attrs;
        }

        public HtmlWriter getWriter() {
            return this.htmlWriter;
        }

        public String getSoftbreak() {
            return HtmlRenderer.this.softbreak;
        }

        public void render(Node node) {
            this.nodeRendererMap.render(node);
        }

        private void setCustomAttributes(Node node, String tagName, Map<String, String> attrs) {
            Iterator iterator = this.attributeProviders.iterator();
            while(iterator.hasNext()) {
                AttributeProvider attributeProvider = (AttributeProvider)iterator.next();
                if (attributeProvider instanceof AbstractAttributeProvider){
                    ((AbstractAttributeProvider)attributeProvider).setAttribute(node,tagName,attrs,ctx);
                }else {
                    attributeProvider.setAttributes(node, tagName, attrs);
                }
            }
        }
    }

    public static class Builder extends org.commonmark.renderer.html.HtmlRenderer.Builder{
        private String softbreak = "\n";
        private boolean escapeHtml = false;
        private boolean sanitizeUrls = false;
        private UrlSanitizer urlSanitizer = new DefaultUrlSanitizer();
        private boolean percentEncodeUrls = false;
        private List<AttributeProviderFactory> attributeProviderFactories = new ArrayList();
        private List<HtmlNodeRendererFactory> nodeRendererFactories = new ArrayList();

        public Builder() {
        }

        public HtmlRenderer build0() {
            return new HtmlRenderer(this);
        }

        public HtmlRenderer.Builder softbreak(String softbreak) {
            this.softbreak = softbreak;
            return this;
        }

        public HtmlRenderer.Builder escapeHtml(boolean escapeHtml) {
            this.escapeHtml = escapeHtml;
            return this;
        }

        public HtmlRenderer.Builder sanitizeUrls(boolean sanitizeUrls) {
            this.sanitizeUrls = sanitizeUrls;
            return this;
        }

        public HtmlRenderer.Builder urlSanitizer(UrlSanitizer urlSanitizer) {
            this.urlSanitizer = urlSanitizer;
            return this;
        }

        public HtmlRenderer.Builder percentEncodeUrls(boolean percentEncodeUrls) {
            this.percentEncodeUrls = percentEncodeUrls;
            return this;
        }

        public HtmlRenderer.Builder attributeProviderFactory(AttributeProviderFactory attributeProviderFactory) {
            if (attributeProviderFactory == null) {
                throw new NullPointerException("attributeProviderFactory must not be null");
            } else {
                this.attributeProviderFactories.add(attributeProviderFactory);
                return this;
            }
        }

        public HtmlRenderer.Builder nodeRendererFactory(HtmlNodeRendererFactory nodeRendererFactory) {
            if (nodeRendererFactory == null) {
                throw new NullPointerException("nodeRendererFactory must not be null");
            } else {
                this.nodeRendererFactories.add(nodeRendererFactory);
                return this;
            }
        }

        public HtmlRenderer.Builder extensions(Iterable<? extends Extension> extensions) {
            if (extensions == null) {
                throw new NullPointerException("extensions must not be null");
            } else {
                Iterator iter = extensions.iterator();

                while(iter.hasNext()) {
                    Extension extension = (Extension)iter.next();
                    if (extension instanceof org.commonmark.renderer.html.HtmlRenderer.HtmlRendererExtension){
                        org.commonmark.renderer.html.HtmlRenderer.HtmlRendererExtension htmlRendererExtension = (org.commonmark.renderer.html.HtmlRenderer.HtmlRendererExtension)extension;
                        htmlRendererExtension.extend(this);
                    }
                }

                return this;
            }
        }
    }
}
